module klang.klang.处理导入模块.处理导入_;
import 全局;
import std.conv: ·to = to;
import std.functional: ·fd = toDelegate;
import ··JSON = klang.编译.json.JSON;
import ··命令行_ = klang.编译.命令行.命令行_;
import ··类型_ = klang.编译.类型.类型_;
import ··诊断_ = klang.编译.诊断.诊断_;
import 基础节点 = klang.编译.语法节点.节点_;
import 核心 = klang.编译.核心.核心_;
import klang.编译.核心.核心_: 整数_, 词典_, 连接, 创建词典, 分割, 查找, 开始包含;
import klang.编译.路径.路径_: 目录分割符, 路径_, 组合路径, 正规化路径, 获取根长度, 获取目录路径;
import klang.编译.类型.枚举值.枚举值_: SK;
import klang.klang.语法节点.Klang节点_: 源文件_ = Klang源文件_, 是有效节点 = 是klang节点, 节点_ = Klang节点_, 遍历节点, 导入声明_, 导入从声明_, 导入成员基类_, 是导入版本成员, 是导入成员, 是导入成员块, 导入成员_, 导入版本成员_, 导入成员块_;
import klang.klang.类型.klang类型_: KSK, 编译选项_, 模块导入位置种类_, klang解析的模块名称_;
import klang.klang.klang: 获取klang根选项, 解析编译选项, 默认支持库文件名, 获取klang模块根选项;
import klang.编译.命令行.命令行_: 编译配置文件;
import klang.编译.类型.类型_: 模块解析缓存_, 解析的模块包括查找失败位置_, 解析的模块名称_, 编译主机接口_, 检查源文件函数_;
import klang.编译.诊断集.诊断集_: 诊断集_;
import klang.klang.常量值.Klang常量值_: 配置种类_;
编译主机接口_ _当前主机;
路径_ _源码路径;
dstring[dstring] _依赖;
模块解析缓存_ _缓存;
dstring _全局模块目录;
dstring _当前模块目录;
dstring _当前源码目录;
dstring _当前项目配置;
struct 依赖目录_ {
    dstring 路径;
    dstring 包名 = ··null!(dstring);
    this(dstring 路径, dstring 包名 = ··null!(dstring)) {
        this.路径 = 路径;
        this.包名 = 包名;
    }
}
struct 找到的包信息_ {
    bool 找到;
    编译选项_ 包内容;
    包ID_ 包Id;
    this(bool 找到, 编译选项_ 包内容, 包ID_ 包Id) {
        this.找到 = 找到;
        this.包内容 = 包内容;
        this.包Id = 包Id;
    }
}
struct 包ID_ {
    dstring 名称;
    dstring 版本 = ··null!(dstring);
    dstring 系统 = ··null!(dstring);
    dstring 架构 = ··null!(dstring);
    dstring 环境 = ··null!(dstring);
    dstring 支持 = ··null!(dstring);
    this(dstring 名称) {
        this.名称 = 名称;
    }
}
struct 模块解析状态_ {
    编译主机接口_ 主机;
    bool 启用追踪;
    dstring[] 查找失败位置;
    this(编译主机接口_ 主机, bool 启用追踪, dstring[] 失败位置) {
        this.主机 = 主机;
        this.启用追踪 = 启用追踪;
        this.查找失败位置 = 失败位置;
    }
}
检查源文件函数_ 创建收集函数(编译主机接口_ 主机) {
    ._当前主机 = 主机;
    编译选项_ _编译选项 = (cast(编译选项_)(_当前主机.获取编译选项()));
    ._源码路径 = new 路径_(_编译选项.源码);
    ._依赖 = _编译选项.依赖;
    ._缓存 = 主机.创建模块解析缓存();
    ··类型_.前端接口_ 前端 = 主机.获取前端();
    dstring 配置文件名称 = 前端.获取语言配置文件名()[0];
    dstring 当前目录 = 主机.获取当前目录() ~ "/测试"d;
    ._全局模块目录 = 主机.获取当前目录() ~ "/测试/模块"d;
    dstring 当前配置文件名称 = 正规化路径(组合路径(当前目录, [配置文件名称]));
    dstring 当前配置文件目录 = 当前目录;
    while(!主机.文件存在(当前配置文件名称) && 获取根长度(当前配置文件目录) != 当前配置文件目录.length) {
        当前配置文件目录 = 获取目录路径(当前配置文件目录);
        当前配置文件名称 = 正规化路径(组合路径(当前配置文件目录, [配置文件名称]));
    }
    if (主机.文件存在(当前配置文件名称)) {
        当前配置文件目录 = 获取目录路径(当前配置文件名称);
    }
    ._当前项目配置 = 当前配置文件名称;
    dstring 临时当前源码目录 = 正规化路径(组合路径(当前配置文件目录, ["源码"d]));
    ._当前模块目录 = 正规化路径(组合路径(当前配置文件目录, ["模块"d]));
    ._当前源码目录 = 正规化路径(组合路径(当前配置文件目录, [._源码路径]));
    if (!主机.目录存在(临时当前源码目录)) {
        if (主机.文件存在(._当前项目配置)) {
            ··命令行_.编译的命令行_ 配置文件编译结果 = 编译配置文件(._当前项目配置, cast(dstring delegate(dstring))(·fd(&主机.读文件)), 获取klang根选项());
            编译选项_ 配置文件选项 = 解析编译选项(配置文件编译结果.选项);
            dstring 中缀 = .根据配置生成目录中缀(配置文件选项);
            临时当前源码目录 = 正规化路径(组合路径(当前目录, [中缀, "源码"d]));
            if (!主机.目录存在(._当前模块目录)) {
                ._当前模块目录 = 组合路径(当前目录, [".."d, "模块"d]);
            }
            ._当前源码目录 = 临时当前源码目录;
        }
    }
    return cast(检查源文件函数_)(·fd(&._收集模块引用));
}
void _收集模块引用(基础节点.源文件_ sou) {
    bool delegate(基础节点.节点_) 收集;
    void delegate(dstring, long, long) 处理导入模块;
    void delegate(导入成员基类_) 收集导入成员模块;
    基础节点.模块导入信息_[] 导入信息组;
    bool 收集·func(基础节点.节点_ 节点) {
        if (是有效节点(节点)) {
            节点_ n = (cast(节点_)(节点));
            long ksk = n.目标种类;
            switch (ksk) {
                case KSK.导入声明: {
                    导入声明_ nn = (cast(导入声明_)(n));
                    if ((nn.导入组 is null) || (nn.导入组.原型 is ··null!(导入成员基类_[])) || nn.导入组.length == 0) {
                        break;
                    }
                    foreach (导入成员基类_ im; nn.导入组) {
                        收集导入成员模块(im);
                    }
                    break;
                }
                case KSK.导入从声明: {
                    导入从声明_ nn = (cast(导入从声明_)(n));
                    处理导入模块(nn.模块名.内容, nn.模块名.开始, nn.模块名.结束);
                    break;
                }
                default: {
                    遍历节点(n, cast(bool delegate(基础节点.节点_))(收集), ··null!(bool delegate(基础节点.节点数组_!(基础节点.节点_))));
                    break;
                }
            }
        }
        return false;
    }
    收集 = &收集·func;
    void 处理导入模块·func(dstring 模块名, long 开始, long 结束) {
        .追加!(基础节点.模块导入信息_)(导入信息组, new 基础节点.模块导入信息_(模块名, 开始, 结束));
    }
    处理导入模块 = &处理导入模块·func;
    void 收集导入成员模块·func(导入成员基类_ 目录) {
        if (是导入成员(目录)) {
            导入成员_ n = (cast(导入成员_)(目录));
            处理导入模块(n.模块名.内容, n.模块名.开始, n.模块名.结束);
        }
        else if (是导入版本成员(目录)) {
            导入版本成员_ n = (cast(导入版本成员_)(目录));
            收集导入成员模块(n.然后语句);
            if (n.否则语句 !is ··null!(导入成员基类_)) {
                收集导入成员模块(n.否则语句);
            }
        }
        else {
            导入成员块_ n = (cast(导入成员块_)(目录));
            foreach (导入成员基类_ s; n.语句组) {
                收集导入成员模块(s);
            }
        }
    }
    收集导入成员模块 = &收集导入成员模块·func;
    if (sou.导入组 !is ··null!(基础节点.模块导入信息_[])) {
        return;
    }
    导入信息组 = [];
    if (sou.种类 == SK.源文件) {
        源文件_ 当前源文件 = (cast(源文件_)(sou));
        遍历节点(当前源文件, cast(bool delegate(基础节点.节点_))(收集), ··null!(bool delegate(基础节点.节点数组_!(基础节点.节点_))));
        sou.导入组 = 导入信息组;
    }
    else {
        throw new Throwable(·to!(string)("不是klang源文件_"d));
    }
}
解析的模块名称_[] 加载klang模块(dstring[] 模块名称组, dstring 包含文件) {
    return ._加载klang模块(模块名称组, 包含文件);
}
解析的模块名称_[] _加载klang模块(dstring[] 模块名称组, dstring 包含文件) {
    return .加载包含本地缓存!(解析的模块名称_)(模块名称组, 包含文件, cast(解析的模块名称_ delegate(dstring, dstring))(·fd(&.解析klang模块)));
}
dstring 根据配置生成目录中缀(编译选项_ 配置) {
    dstring[] 中缀 = [];
    if (配置.架构 !is ··null!(dstring)) {
        .追加!(dstring)(中缀, 配置.架构);
    }
    if (配置.支持 !is ··null!(dstring)) {
        .追加!(dstring)(中缀, 配置.支持);
    }
    if (配置.系统 !is ··null!(dstring)) {
        .追加!(dstring)(中缀, 配置.系统);
    }
    if (配置.环境 !is ··null!(dstring)) {
        .追加!(dstring)(中缀, 配置.环境);
    }
    if (配置.版本 !is ··null!(dstring)) {
        return 连接(中缀, "-"d) ~ 目录分割符 ~ 配置.版本;
    }
    return 连接(中缀, "-"d);
}
解析的模块包括查找失败位置_ 解析模块(dstring 模块名称, dstring 包含文件, 编译主机接口_ 主机, 模块解析缓存_ 缓存) {
    主机.追踪(诊断集_.Resolving_module_0_from_1, [模块名称, 包含文件]);
    dstring containingDirectory = 获取目录路径(包含文件);
    核心.词典_!(解析的模块包括查找失败位置_) perFolderCache = 缓存.获取或创建目录的缓存(containingDirectory);
    if ((perFolderCache !is null) && (perFolderCache.原型 !is ··null!(解析的模块包括查找失败位置_[dstring])) && perFolderCache.具有(模块名称)) {
        主机.追踪(诊断集_.Resolution_for_module_0_was_found_in_cache_from_location_1, [模块名称, containingDirectory]);
        return perFolderCache.获取(模块名称);
    }
    dstring 当前配置文件目录 = 正规化路径(containingDirectory);
    ··类型_.前端接口_ 前端 = 主机.获取前端();
    dstring 配置文件名称 = 前端.获取语言配置文件名()[0];
    dstring 当前配置文件名称 = 正规化路径(组合路径(当前配置文件目录, [配置文件名称]));
    while(!主机.文件存在(当前配置文件名称) && 获取根长度(当前配置文件目录) != 当前配置文件目录.length) {
        当前配置文件目录 = 获取目录路径(当前配置文件目录);
        当前配置文件名称 = 正规化路径(组合路径(当前配置文件目录, [配置文件名称]));
    }
    if (!主机.文件存在(当前配置文件名称)) {
        主机.追踪(诊断集_.Resolving_module_0_from_1, [模块名称, 包含文件]);
    }
    dstring 当前源码目录 = 组合路径(获取目录路径(当前配置文件名称), ["源码"d]);
    dstring[] 查找失败位置 = [];
    if (!主机.目录存在(当前源码目录)) {
        ··命令行_.编译的命令行_ 配置文件编译结果 = 编译配置文件(当前配置文件名称, cast(dstring delegate(dstring))(·fd(&主机.读文件)), 获取klang根选项());
        if (配置文件编译结果.错误组 !is ··null!(··诊断_.诊断_[])) {
            return new 解析的模块包括查找失败位置_(查找失败位置, ··null!(解析的模块名称_));
        }
        编译选项_ 配置文件选项 = 解析编译选项(配置文件编译结果.选项);
        dstring 中缀 = .根据配置生成目录中缀(配置文件选项);
        当前源码目录 = 组合路径(获取目录路径(当前源码目录), [中缀, "源码"d]);
    }
    dstring 当前模块目录 = ._当前模块目录;
    dstring 全局模块目录 = ._全局模块目录;
    dstring 模块配置文件名 = 前端.获取语言配置文件名()[1];
    dstring 当前项目配置 = 组合路径(获取目录路径(当前配置文件名称), [模块配置文件名]);
    核心.词典_!(依赖目录_) 依赖目录 = 创建词典!(依赖目录_)();
    ··命令行_.编译的命令行_ 当前配置编译结果 = 编译配置文件(当前项目配置, cast(dstring delegate(dstring))(·fd(&主机.读文件)), 获取klang模块根选项());
    if (当前配置编译结果.错误组 !is ··null!(··诊断_.诊断_[])) {
        return new 解析的模块包括查找失败位置_(查找失败位置, ··null!(解析的模块名称_));
    }
    编译选项_ 配置选项 = 解析编译选项(当前配置编译结果.选项, 配置种类_.KlangModConfig);
    dstring[dstring] 依赖 = 配置选项.依赖;
    模块解析状态_ state = .模块解析状态_(主机, true, 查找失败位置);
    编译选项_ 编译选项 = (cast(编译选项_)(主机.获取编译选项()));
    dstring op系统 = 编译选项.系统;
    dstring op架构 = 编译选项.架构;
    dstring op环境 = 编译选项.环境;
    dstring op支持 = 编译选项.支持;
    if (依赖 !is ··null!(dstring[dstring])) {
        foreach (dstring k, dstring 版本号; 依赖) {
            dstring[] 中缀 = [];
            dstring 包名称 = ""d;
            找到的包信息_ 包信息 = .获取Klang包信息(组合路径(当前模块目录, [k]), false, state);
            if (!包信息.找到) {
                包信息 = .获取Klang包信息(组合路径(全局模块目录, [k]), false, state);
            }
            if (包信息.找到) {
                dstring 名称 = 包信息.包Id.名称;
                dstring 系统 = 包信息.包Id.系统;
                dstring 架构 = 包信息.包Id.架构;
                dstring 环境 = 包信息.包Id.环境;
                dstring 支持 = 包信息.包Id.支持;
                dstring[] 系统组 = [];
                dstring[] 架构组 = [];
                dstring[] 环境组 = [];
                if (名称 !is ··null!(dstring)) {
                    包名称 = 名称;
                }
                正则_!(dchar) 正则 = ·regex(r" "d, "g"c);
                if (系统 !is ··null!(dstring)) {
                    系统组 = 分割(正则.p·替换全部(系统, ""d), [","d]);
                }
                if (架构 !is ··null!(dstring)) {
                    架构组 = 分割(正则.p·替换全部(架构, ""d), [","d]);
                }
                if (环境 !is ··null!(dstring)) {
                    环境组 = 分割(正则.p·替换全部(环境, ""d), [","d]);
                }
                dstring ok = 查找!(dstring)(架构组, (dstring s) { return 开始包含(s, op架构); });
                if (ok !is ··null!(dstring)) {
                    中缀 = .追加!(dstring)(中缀, op架构);
                    ok = op支持 == 支持 ? 支持 : ··null!(dstring);
                    if (ok !is ··null!(dstring)) {
                        中缀 = .追加!(dstring)(中缀, 支持);
                        ok = 查找!(dstring)(系统组, (dstring s) { return 开始包含(s, op系统); });
                        if (ok !is ··null!(dstring)) {
                            中缀 = .追加!(dstring)(中缀, op系统);
                            ok = 查找!(dstring)(环境组, (dstring s) { return 开始包含(s, op环境); });
                            if (ok !is ··null!(dstring)) {
                                中缀 = .追加!(dstring)(中缀, op环境);
                            }
                        }
                    }
                }
            }
            依赖目录_ ···tempS·0·_1;
            {
                ···tempS·0·_1.路径 = 组合路径(k, [连接(中缀, "-"d), 版本号]);
                ···tempS·0·_1.包名 = 包名称;
            }
            依赖目录.p·设置(k, ···tempS·0·_1);
        }
    }
    klang解析的模块名称_ 结果 = .获取结果(模块名称, 主机, 当前源码目录, 模块导入位置种类_.无_);
    if (结果 !is ··null!(klang解析的模块名称_)) {
        return new 解析的模块包括查找失败位置_(查找失败位置, 结果);
    }
    结果 = .获取结果(模块名称, 主机, 当前模块目录, 模块导入位置种类_.外部支持库_, 依赖目录);
    if (结果 !is ··null!(klang解析的模块名称_)) {
        return new 解析的模块包括查找失败位置_(查找失败位置, 结果);
    }
    结果 = .获取结果(模块名称, 主机, 全局模块目录, 模块导入位置种类_.全局支持库_, 依赖目录);
    if (结果 !is ··null!(klang解析的模块名称_)) {
        return new 解析的模块包括查找失败位置_(查找失败位置, 结果);
    }
    return new 解析的模块包括查找失败位置_(查找失败位置, ··null!(解析的模块名称_));
}
解析的模块名称_ 解析klang模块(dstring 模块名称, dstring 包含文件) {
    解析的模块名称_ 模块名 = .解析模块(模块名称, 包含文件, ._当前主机, ._缓存).模块目录路径;
    dstring[] 文件名组 = _当前主机.读目录(模块名.解析的模块路径, _当前主机.获取前端().获取支持的扩展());
    模块名.全部源码文件名 = 文件名组;
    return 模块名;
}
/** @内部 */
T[] 加载包含本地缓存(T)(dstring[] 模块名称组, dstring 包含文件, T delegate(dstring, dstring) 加载) {
    if (模块名称组.length == 0) {
        return [];
    }
    T[] 结果组 = [];
    核心.词典_!(T) 缓存 = 创建词典!(T)();
    foreach (dstring 名称; 模块名称组) {
        T 结果;
        if (缓存.具有(名称)) {
            结果 = 缓存.获取(名称);
        }
        else {
            结果 = 加载(名称, 包含文件);
            缓存.p·设置(名称, 结果);
        }
        .追加!(T)(结果组, 结果);
    }
    return 结果组;
}
dstring 路径转为klang模块配置(dstring 目录, 编译主机接口_ 主机) {
    ··类型_.前端接口_ 前端 = 主机.获取前端();
    dstring 配置文件名称 = 前端.获取语言配置文件名()[1];
    return 组合路径(目录, [配置文件名称]);
}
找到的包信息_ 获取Klang包信息(dstring 模块目录, bool 仅记录失败的, 模块解析状态_ 状态) {
    编译主机接口_ 主机 = 状态.主机;
    bool 启用追踪 = 状态.启用追踪;
    bool 目录存在 = !仅记录失败的 && 主机.目录存在(模块目录);
    dstring klang包路径 = .路径转为klang模块配置(模块目录, 主机);
    if (目录存在 && 主机.文件存在(klang包路径)) {
        ··命令行_.编译的命令行_ 配置文件编译结果 = 编译配置文件(klang包路径, cast(dstring delegate(dstring))(·fd(&主机.读文件)), 获取klang模块根选项());
        if (配置文件编译结果.错误组 !is ··null!(··诊断_.诊断_[])) {
            return .找到的包信息_(false, ··null!(编译选项_), ··null!(包ID_));
        }
        编译选项_ 配置文件选项 = 解析编译选项(配置文件编译结果.选项, 配置种类_.KlangModConfig);
        包ID_ klangId;
        if (配置文件选项.名称 !is ··null!(dstring)) {
            klangId = .包ID_(配置文件选项.名称);
            klangId.版本 = 配置文件选项.版本;
            klangId.系统 = 配置文件选项.系统;
            klangId.架构 = 配置文件选项.架构;
            klangId.环境 = 配置文件选项.环境;
            klangId.支持 = 配置文件选项.支持;
        }
        if (启用追踪) {
            if (klangId !is ··null!(包ID_)) {
                主机.追踪(诊断集_.Found_package_json_at_0_Package_ID_is_1, [klang包路径, klangId.名称]);
            }
            else {
                主机.追踪(诊断集_.Found_package_json_at_0, [klang包路径]);
            }
        }
        return .找到的包信息_(true, 配置文件选项, klangId);
    }
    else {
        if (目录存在 && 启用追踪) {
            主机.追踪(诊断集_.File_0_does_not_exist, [klang包路径]);
        }
        return .找到的包信息_(false, ··null!(编译选项_), ··null!(包ID_));
    }
}
klang解析的模块名称_ 获取结果(dstring 模块名称, 编译主机接口_ 主机, dstring 基础目录, 模块导入位置种类_ 种类, 核心.词典_!(依赖目录_) 依赖 = ··null!(核心.词典_!(依赖目录_))) {
    依赖目录_ ···tempS·0·_2;
    {
        ···tempS·0·_2.路径 = ""d;
    }
    依赖目录_ 模块目录 = ···tempS·0·_2;
    if ((依赖 is null) || (依赖.原型 is ··null!(依赖目录_[dstring]))) {
        依赖目录_ ···tempS·0·_3;
        {
            ···tempS·0·_3.路径 = 正规化路径(组合路径(基础目录, [模块名称]));
        }
        模块目录 = ···tempS·0·_3;
    }
    else if (依赖.具有(模块名称)) {
        依赖目录_ ···tempS·0·_4;
        {
            ···tempS·0·_4.路径 = 正规化路径(组合路径(基础目录, [依赖.获取(模块名称).路径]));
            ···tempS·0·_4.包名 = 依赖.获取(模块名称).包名;
        }
        模块目录 = ···tempS·0·_4;
    }
    if (主机.目录存在(模块目录.路径)) {
        return new klang解析的模块名称_(模块名称, 模块目录.路径, [], 种类, 模块目录.包名);
    }
    if (种类 == 模块导入位置种类_.无_) {
        return ··null!(klang解析的模块名称_);
    }
    依赖目录_ ···tempS·0·_5;
    {
        ···tempS·0·_5.路径 = 正规化路径(组合路径(._全局模块目录, [模块名称]));
    }
    模块目录 = ···tempS·0·_5;
    if (主机.目录存在(模块目录.路径)) {
        if ((依赖 !is null) && (依赖.原型 !is ··null!(依赖目录_[dstring])) && 依赖.具有(模块名称)) {
            模块目录 = 依赖.获取(模块名称);
            依赖目录_ ···tempS·0·_6;
            {
                ···tempS·0·_6.路径 = 正规化路径(组合路径(._全局模块目录, [模块目录.路径]));
                ···tempS·0·_6.包名 = 模块目录.包名;
            }
            模块目录 = ···tempS·0·_6;
            种类 = 模块导入位置种类_.从全局位置导入的外部模块_;
        }
        return new klang解析的模块名称_(模块名称, 模块目录.路径, [], 种类, 模块目录.包名);
    }
    return ··null!(klang解析的模块名称_);
}
dstring 获取默认支持库名称() {
    return 组合路径(._全局模块目录, ["lib"d, 默认支持库文件名]);
}
